#include <utils/err.h>
#include <seminix/cache.h>
#include <seminix/syscall.h>
#include <seminix/tcb.h>
#include <cap/ipc_buffer.h>
#include <cap/cap.h>
#include <cap/endpoint.h>
#include <cap/cnode.h>
#include <cap/thread.h>
#include <cap/irq.h>
#include <cap/frame.h>
#include <cap/vspace.h>
#include <cap/rlimit.h>
#include <cap/frame.h>

static const struct cap_ops *const cap_ops[cap_count_max] __ro_after_init = {
    [cap_null_cap]      = NULL,
    [cap_rlimit_cap]    = &rlimit_cap_ops,
    [cap_cnode_cap]     = &cnode_cap_ops,
    [cap_thread_cap]    = &thread_cap_ops,
    [cap_devctl_cap]    = NULL,
    [cap_device_cap]    = NULL,
    [cap_devirq_cap]    = NULL,
    [cap_deadline_cap]  = NULL,
    [cap_vspace_cap]    = &vspace_cap_ops,
    [cap_endpoint_cap]  = &endpoint_cap_ops,
    [cap_sigctl_cap]    = NULL,
    [cap_sysctl_cap]    = NULL,
    [cap_frame_cap]     = &frame_cap_ops,
    [cap_page_cap]      = NULL,
};

static const struct cap_ops *get_cap_ops(int type)
{
    const struct cap_ops *ops;

    if (seminix_cap_type_invalid(type))
        return NULL;
    ops = cap_ops[type];
    assert(ops);
    return ops;
}

static void cap_init(cap_t *cap, cap_t *parent, int type)
{
    cap->flags = 0;
    cap_set_cap_type(cap, type);
    cap->parent = parent;
    INIT_LIST_HEAD(&cap->child);
    spin_lock_init(&cap->lock);
    atomic_set(&cap->refcount, 1);
    cap->index = -1;
    cap->cap_cnode = NULL;
    if (parent) {
        assert(spin_is_locked(&parent->lock));
        list_add(&cap->list, &parent->child);
    }
}

static void cap_rights_update(cap_t *cap, cap_t *new_cap, int rights)
{
    int type = cap_get_cap_type(cap);

    if (type != cap_rlimit_cap)
        BUG_ON(type != cap_get_cap_type(new_cap));

    if (cap_can_dup(cap) && seminix_cap_rights_allow_dup(rights))
        cap_set_can_dup(new_cap);
    if (cap_can_move(cap) && seminix_cap_rights_allow_move(rights))
        cap_set_can_move(new_cap);
    if (cap_can_send(cap) && seminix_cap_rights_allow_send(rights))
        cap_set_can_send(new_cap);
    if (cap_can_recv(cap) && seminix_cap_rights_allow_recv(rights))
        cap_set_can_recv(new_cap);
    if (cap_can_write(cap) && seminix_cap_rights_allow_write(rights))
        cap_set_can_write(new_cap);
    if (cap_can_create(cap) && seminix_cap_rights_allow_create(rights))
        cap_set_can_create(new_cap);
}

static cap_t *cap_create_locked(cap_t *cap, int rights, seminix_object_t *object)
{
    int ret = -SERRNO_EILLEGAL;
    cap_t *new_cap;
    int type = cap_get_cap_type(cap);
    int new_type = object_to_cap_type(object->type);
    const struct cap_ops *ops;

    if (type != cap_rlimit_cap || !cap_can_create(cap))
        goto out;

    ops = get_cap_ops(new_type);
    if (!ops || !ops->cap_create)
        goto out;
    assert(ops->cap_delete);

    new_cap = ops->cap_create(object);
    if (IS_ERR(new_cap))
        return new_cap;

    cap_init(new_cap, cap, new_type);
    cap_rights_update(cap, new_cap, rights);

    return new_cap;
out:
    return ERR_PTR(ret);
}

static cap_t *cap_dup_locked(cap_t *cap, int rights, seminix_object_t *object)
{
    int ret = -SERRNO_EILLEGAL;
    cap_t *new_cap;
    int type = cap_get_cap_type(cap);
    const struct cap_ops *ops;

    if (!cap_can_dup(cap))
        goto out;

    ops = get_cap_ops(type);
    if (!ops || !ops->cap_dup)
        goto out;
    assert(ops->cap_revoke);

    new_cap = ops->cap_dup(cap);
    if (IS_ERR(new_cap))
        return new_cap;

    cap_init(new_cap, cap, type);
    cap_set_revocable(new_cap);
    cap_rights_update(cap, new_cap, rights);

    if (ops->cap_complete_dup)
        ops->cap_complete_dup(cap, new_cap);

    return new_cap;
out:
    return ERR_PTR(ret);
}

static cap_t *do_cap_creating(cap_t *cap, int rights, seminix_object_t *object, bool create)
{
    cap_t *new_cap;
    cap_t *(*do_cap_func)(cap_t *, int, seminix_object_t *) = cap_dup_locked;

    if (create)
        do_cap_func = cap_create_locked;

    spin_lock(&cap->lock);
    if (cap_removing(cap)) {
        spin_unlock(&cap->lock);
        return ERR_PTR(-SERRNO_EREMOVING);
    }
    new_cap = do_cap_func(cap, rights, object);
    spin_unlock(&cap->lock);
    return new_cap;
}

cap_t *cap_create(cap_t *cap, int rights, seminix_object_t *object)
{
    return do_cap_creating(cap, rights, object, true);
}

cap_t *cap_dup(cap_t *cap, int rights)
{
    return do_cap_creating(cap, rights, NULL, false);
}

static int cap_parent_check(cap_t *parent, cap_t *cap)
{
    if (!parent || !cap)
        return -SERRNO_EINVAL;
    if (!cap_is_parent(parent, cap))
        return -SERRNO_EILLEGAL;
    return 0;
}

int cap_revoke(cap_t *parent, cap_t *cap)
{
    int ret;
    cap_t *this_cap;
    LIST_HEAD(delete_list);

    ret = cap_parent_check(parent, cap);
    if (ret)
        return ret;

    spin_lock(&cap->lock);
    list_for_each_entry(this_cap, &cap->child, list) {
        list_del(&this_cap->list);
        list_add(&this_cap->list, &delete_list);
    }
    spin_unlock(&cap->lock);

    list_for_each_entry(this_cap, &delete_list, list) {
        this_cap->parent = NULL;
        spin_lock(&this_cap->lock);
        cap_set_removing(cap);
        spin_unlock(&this_cap->lock);
        capput(this_cap);
    }

    return ret;
}

int cap_delete(cap_t *parent, cap_t *cap)
{
    int ret;

    assert(!cap_removing(cap));
    ret = cap_parent_check(parent, cap);
    if (ret)
        return ret;

    spin_lock(&parent->lock);
    list_del(&cap->list);
    spin_unlock(&parent->lock);

    cap->parent = NULL;
    spin_lock(&cap->lock);
    cap_set_removing(cap);
    spin_unlock(&cap->lock);
    capput(cap);
    return 0;
}

static inline unsigned long __cap_rlimit(cap_t *cap, bool dup, seminix_object_t *object)
{
    int type;
    const struct cap_ops *ops;

    if (dup)
        type = cap_get_cap_type(cap);
    else
        type = object_to_cap_type(object->type);
    ops = get_cap_ops(type);
    if (!ops)
        return 1;
    if (dup)
        return ops->cap_dup_rlimit ? ops->cap_dup_rlimit(cap) : 1;
    else
        return ops->cap_create_rlimit ? ops->cap_create_rlimit(object) : 1;
}

unsigned long cap_dup_rlimit(cap_t *cap)
{
    return __cap_rlimit(cap, true, NULL);
}

unsigned long cap_create_rlimit(seminix_object_t *object)
{
    return __cap_rlimit(NULL, false, object);
}

unsigned long cap_delete_rlimit(cap_t *cap)
{
    int type = cap_get_cap_type(cap);
    const struct cap_ops *ops = get_cap_ops(type);

    if (!ops)
        return 1;
    return ops->cap_delete_rlimit ? ops->cap_delete_rlimit(cap) : 1;
}

static void cap_remove(cap_t *cap)
{
    cap_t *this_cap;
    const struct cap_ops *ops;

    assert(cap_removing(cap));

    list_for_each_entry(this_cap, &cap->child, list) {
        list_del(&this_cap->list);
        this_cap->parent = NULL;
        spin_lock(&this_cap->lock);
        cap_set_removing(this_cap);
        spin_unlock(&this_cap->lock);
        capput(this_cap);
    }
    BUG_ON(!list_empty(&cap->child));
    BUG_ON(spin_is_locked(&cap->lock));

    ops = get_cap_ops(cap_get_cap_type(cap));
    BUG_ON(!ops);
    cnode_cap_free_slot_cap(cap);
    rlimit_down(cap);
    if (cap_revocable(cap))
        ops->cap_revoke(cap);
    else
        ops->cap_delete(cap);
}

void capput(cap_t *cap)
{
    if (atomic_dec_and_test(&cap->refcount))
        cap_remove(cap);
}

static int const object_to_cap[seminix_ObjectTypeCount] = {
    [0 ... seminix_ObjectTypeCount - 1] = cap_null_cap,
    [seminix_RlimitObject] = cap_rlimit_cap,
    [seminix_CnodeObject] = cap_cnode_cap,
    [seminix_TcbObject] = cap_thread_cap,
    [seminix_DeadlineObject] = cap_deadline_cap,
    [seminix_VspaceObject] = cap_vspace_cap,
    [seminix_EndpointObject] = cap_endpoint_cap,
    [seminix_FrameObject] = cap_frame_cap,
};

int object_to_cap_type(seminix_object_type_t object_type)
{
    if (object_type < seminix_ObjectTypeCount)
        return object_to_cap[object_type];
    return cap_null_cap;
}
